﻿/************************************************************************************
 * xujiajun@yoozoo.com 御剑
 * 显示命令队列的管理器
 ***********************************************************************************/

using System;
using System.Collections.Generic;
using WorldMapBase.Tile;
using Yoozoo.Managers;

namespace WorldMapBase.Display
{
    /// <summary>
    /// 用于保存显示元素和链表节点映射的辅助类（这个只针对隐藏命令使用）
    /// </summary>
    internal class ElementNodeDic
    {
        /// <summary>
        /// 资源ID和链表节点的映射字典
        /// </summary>
        private Dictionary<int,DisplayCmdHashContainer> m_ResID2NodeDic = new Dictionary<int, DisplayCmdHashContainer>(10);

        /// <summary>
        /// 显示元素和链表节点的映射字典
        /// </summary>
        private Dictionary<IDisplayElement, LinkedListNode<DisplayCommand>> m_Element2NodeDic = new Dictionary<IDisplayElement, LinkedListNode<DisplayCommand>>(200);

        /// <summary>
        /// 添加元素和节点的映射
        /// </summary>
        /// <param name="element">显示元素</param>
        /// <param name="node">链表节点</param>
        internal void Add(IDisplayElement element, LinkedListNode<DisplayCommand> node)
        {
            // 获取命令的资源ID
            var resID = node.Value.GetResID();

            DisplayCmdHashContainer container;
            if (!m_ResID2NodeDic.TryGetValue(resID, out container))
            {
                container = ClassManager.Get<DisplayCmdHashContainer>();
                m_ResID2NodeDic.Add(resID, container);
            }
            container.set.Add(node);
            m_Element2NodeDic.Add(element, node);
        }

        /// <summary>
        /// 通过指定显示元素获取节点
        /// </summary>
        /// <param name="element">显示元素</param>
        /// <returns>链表节点</returns>
        internal LinkedListNode<DisplayCommand> Get(IDisplayElement element)
        {
            LinkedListNode<DisplayCommand> node;
            m_Element2NodeDic.TryGetValue(element, out node);
            return node;
        }

        /// <summary>
        /// 通过指定资源路径获取节点
        /// </summary>
        /// <param name="resID">资源ID</param>
        /// <returns>链表节点</returns>
        internal LinkedListNode<DisplayCommand> Get(int resID)
        {
            // 寻找该资源路径有没有节点
            DisplayCmdHashContainer container;
            if (m_ResID2NodeDic.TryGetValue(resID, out container))
            {
                LinkedListNode<DisplayCommand> node = null;
                foreach (var n in container.set)
                {
                    node = n;
                    break;
                }

                return node;
            }
            return null;
        }

        /// <summary>
        /// 移除指定链表节点的记录
        /// </summary>
        /// <param name="node">链表节点</param>
        internal void RemoveNode(LinkedListNode<DisplayCommand> node)
        {
            DisplayCmdHashContainer container;
            if (m_ResID2NodeDic.TryGetValue(node.Value.GetResID(), out container))
            {
                if (container.set.Remove(node))
                {
                    var element = node.Value.element;
                    if (m_Element2NodeDic.ContainsKey(element))
                    {
                        m_Element2NodeDic.Remove(element);
                    }
                }
            }
        }

        /// <summary>
        /// 清空数据操作
        /// </summary>
        internal void Clear()
        {
            foreach (var displayCmdHashContainer in m_ResID2NodeDic)
            {
                ClassManager.Free(displayCmdHashContainer.Value); 
            }
            
            m_ResID2NodeDic.Clear();
            m_Element2NodeDic.Clear();
        }
    }

    /// <summary>
    /// 辅助用的命令池，在池的作用外，还存有和隐藏命令相关的数据结构，便于搜索指定的隐藏命令
    /// </summary>
    internal class CommandPool
    {
        /// <summary>
        /// 展示命令节点的堆栈
        /// </summary>
        private readonly Stack<LinkedListNode<DisplayCommand>> m_ShowCommandNodeStack = new Stack<LinkedListNode<DisplayCommand>>(2000);

        /// <summary>
        /// 隐藏命令节点的堆栈
        /// </summary>
        private readonly Stack<LinkedListNode<DisplayCommand>> m_HideCommandNodeStack = new Stack<LinkedListNode<DisplayCommand>>(2000);
        
        /// <summary>
        /// 显示元素和链表节点映射
        /// </summary>
        private readonly ElementNodeDic m_ElementNodeDic = new ElementNodeDic();


        public CommandPool()
        {
            for (int i = 0; i < 2000; i++)
            {
                m_ShowCommandNodeStack.Push(new LinkedListNode<DisplayCommand>(new ShowCommand()));
                m_HideCommandNodeStack.Push(new LinkedListNode<DisplayCommand>(new HideCommand()));
            }
        }
        
        /// <summary>
        /// 取出一个展示命令
        /// </summary>
        /// <param name="resID">资源ID</param>
        /// <param name="callback">执行完成的回调</param>
        /// <returns>命令节点</returns>
        internal LinkedListNode<DisplayCommand> PopShowCommand(int lodIndex,int mapLayer, int resID, CommandTileElement tileElement)
        {
            if (m_ShowCommandNodeStack.Count > 0)
            {
                var node = m_ShowCommandNodeStack.Pop();
                var showCommand = node.Value as ShowCommand;
                showCommand.SetData(lodIndex,mapLayer,resID, tileElement);
                return node;
            }
            else
            {
                ShowCommand showCommand = new ShowCommand();
                showCommand.SetData(lodIndex,mapLayer,resID, tileElement);
                return new LinkedListNode<DisplayCommand>(showCommand);
            }
        }

        /// <summary>
        /// 取出一个隐藏命令
        /// </summary>
        /// <param name="element">待隐藏的元素</param>
        /// <returns>命令节点</returns>
        internal LinkedListNode<DisplayCommand> PopHideCommand(IDisplayElement element)
        {
            LinkedListNode<DisplayCommand> node;
            if (m_HideCommandNodeStack.Count > 0)
            {
                node = m_HideCommandNodeStack.Pop();
                var hideCommand = node.Value as HideCommand;
                hideCommand.SetData(element);
            }
            else
            {
                HideCommand hideCommand = new HideCommand();
                hideCommand.SetData(element);
                node = new LinkedListNode<DisplayCommand>(hideCommand); 
            }
            m_ElementNodeDic.Add(element, node);
            return node;
        }

        /// <summary>
        /// 返还一个命令节点
        /// </summary>
        /// <param name="node"></param>
        internal void PushCommand(LinkedListNode<DisplayCommand> node)
        {
            if (node.Value is HideCommand)
            {
                m_ElementNodeDic.RemoveNode(node);
                m_HideCommandNodeStack.Push(node);
            }
            else
            {
                m_ShowCommandNodeStack.Push(node);
            }
        }

        /// <summary>
        /// 尝试获取已经取出的有关指定元素的隐藏命令
        /// </summary>
        /// <param name="element">已隐藏的元素</param>
        /// <returns>命令节点</returns>
        internal LinkedListNode<DisplayCommand> TryGetPoppedHideCommand(IDisplayElement element)
        {
            return m_ElementNodeDic.Get(element);
        }

        /// <summary>
        /// 尝试获取已经取出的有关指定资源路径的隐藏命令
        /// </summary>
        /// <param name="resID">资源ID</param>
        /// <returns>命令节点</returns>
        internal LinkedListNode<DisplayCommand> TryGetPoppedHideCommand(int resID)
        {
            return m_ElementNodeDic.Get(resID);
        }

        /// <summary>
        /// 清空数据操作
        /// </summary>
        internal void Clear()
        {
            m_ShowCommandNodeStack.Clear();
            m_HideCommandNodeStack.Clear();
            m_ElementNodeDic.Clear();
        }
    }

    /// <summary>
    /// 显示命令队列的管理器
    /// </summary>
    public class DisplayCommandManager : IDisplayCommandInterface
    {
        /// <summary>
        /// 单次处理最大耗时
        /// </summary>
        private const int MAX_PER_FRAME_CONSUME_TIME = 5;

        /// <summary>
        /// 当前剩余待处理的命令数量
        /// </summary>
        internal int commandCount
        {
            get
            {
                return Math.Max( m_CommandLinkedList.Count, m_CommandID2NodeDic.Count );    // 取最大为了观察BUG方便
            }
        }

        /// <summary>
        /// 单次处理计时
        /// </summary>
        private readonly System.Diagnostics.Stopwatch m_Stopwatch = new System.Diagnostics.Stopwatch();

        /// <summary>
        /// 命令链表
        /// </summary>
        private readonly LinkedList<DisplayCommand> m_CommandLinkedList = new LinkedList<DisplayCommand>();

        /// <summary>
        /// 命令ID和命令的映射字典
        /// </summary>
        private readonly Dictionary<int, LinkedListNode<DisplayCommand>> m_CommandID2NodeDic = new Dictionary<int, LinkedListNode<DisplayCommand>>(2000);

        /// <summary>
        /// 命令池子
        /// </summary>
        private readonly CommandPool m_CommandPool = new CommandPool();

        /// <summary>
        /// 抽象显示元素工厂
        /// </summary>
        private DisplayElementFactory m_DisplayElementFactory;

        /// <summary>
        /// 用于生成命令ID的计数
        /// </summary>
        private int m_CommandIndex = 0;

        public int AddHideCommand(IDisplayElement element)
        {
            if (element == null)
            {
                UnityEngine.Debug.LogError("添加隐藏命令时元素不能为空");
                return -1;
            }
            else
            {
                var node = m_CommandPool.TryGetPoppedHideCommand(element);
                if (node == null)
                {
                    return AddCommandNode(m_CommandPool.PopHideCommand(element));
                }
                else
                {
                    return node.Value.commandID;
                }
            }
        }

        public int AddShowCommand(int lodIndex,int mapLayer, int resID, CommandTileElement tileElement)
        {
            var node = m_CommandPool.PopShowCommand(lodIndex, mapLayer, resID, tileElement);
            if (mapLayer == WorldEntry.PLANE_LAYER_ID)
            {
                var command = node.Value;
                command.Execute(m_DisplayElementFactory);
                return -1;
            }
            else
            {
                return AddCommandNode(node);
            }
        }


        public bool HasCommand(int commandID)
        {
            return m_CommandID2NodeDic.ContainsKey(commandID);
        }


        public bool RemoveCommand(int commandID)
        {
            if (commandID < 0) return false;
            LinkedListNode<DisplayCommand> node;
            if (m_CommandID2NodeDic.TryGetValue(commandID, out node))
            {
                m_CommandLinkedList.Remove(node);
                m_CommandID2NodeDic.Remove(commandID);
                m_CommandPool.PushCommand(node);
                return true;
            }
            return false;
        }

        public IDisplayElement TryRemoveHideCommandAndGetElement(int resID)
        {
            var node = m_CommandPool.TryGetPoppedHideCommand(resID);
            if (node != null)
            {
                var commandID = node.Value.commandID;
                m_CommandID2NodeDic.Remove(commandID);
                m_CommandLinkedList.Remove(node);
                m_CommandPool.PushCommand(node);
                return node.Value.element;
            }

            return null;
        }

        /// <summary>
        /// 设置抽象显示元素工厂
        /// </summary>
        /// <param name="factory">工厂对象</param>
        public void SetFactory(DisplayElementFactory factory)
        {
            m_DisplayElementFactory = factory;
        }

        /// <summary>
        /// 清空显示队列数据
        /// </summary>
        public void Clear()
        {
            m_CommandLinkedList.Clear();
            m_CommandID2NodeDic.Clear();
            m_CommandPool.Clear();
            m_DisplayElementFactory.Destroyfactory();
            m_DisplayElementFactory = null;
        }

        /// <summary>
        /// 轮询更新
        /// </summary>
        public void Update()
        {
            m_Stopwatch.Reset();
            m_Stopwatch.Start();
            while (m_CommandLinkedList.Count > 0 && m_Stopwatch.ElapsedMilliseconds < MAX_PER_FRAME_CONSUME_TIME)
            {
                var node = m_CommandLinkedList.First;
                var command = node.Value;
                command.Execute(m_DisplayElementFactory);
                m_CommandLinkedList.RemoveFirst();
                m_CommandID2NodeDic.Remove(command.commandID);
                m_CommandPool.PushCommand(node);
            }
            m_Stopwatch.Stop();
        }

        private int AddCommandNode(LinkedListNode<DisplayCommand> node)
        {
            m_CommandIndex++;
            m_CommandLinkedList.AddLast(node);
            m_CommandID2NodeDic.Add(m_CommandIndex, node);
            node.Value.commandID = m_CommandIndex;
            return m_CommandIndex;
        }
    }

}
